# AssociationTable.java

    package org.codefilarete.stalactite.engine.runtime;

    import java.util.HashMap;
    import java.util.Map;

    import org.codefilarete.stalactite.dsl.naming.AssociationTableNamingStrategy.ReferencedColumnNames;
    import org.codefilarete.stalactite.dsl.naming.ForeignKeyNamingStrategy;
    import org.codefilarete.stalactite.sql.ddl.structure.Column;
    import org.codefilarete.stalactite.sql.ddl.structure.Database.Schema;
    import org.codefilarete.stalactite.sql.ddl.structure.Key;
    import org.codefilarete.stalactite.sql.ddl.structure.Key.KeyBuilder;
    import org.codefilarete.stalactite.sql.ddl.structure.PrimaryKey;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;

    /**
     * @author Guillaume Mary
     */
    public class AssociationTable<
    		SELF extends AssociationTable<SELF, LEFTTABLE, RIGHTTABLE, LEFTID, RIGHTID>,
    		LEFTTABLE extends Table<LEFTTABLE>,
    		RIGHTTABLE extends Table<RIGHTTABLE>,
    		LEFTID,
    		RIGHTID>
    		extends Table<SELF> {

    	/**
    	 * Foreign key pointing to left table primary key
    	 * Expected to be joined with {@link #oneSideKey}
    	 */
    	private final Key<SELF, LEFTID> oneSideForeignKey;

    	/**
    	 * Primary key of source entities table
    	 * Expected to be joined with {@link #oneSideForeignKey}
    	 */
    	private final PrimaryKey<LEFTTABLE, LEFTID> oneSideKey;

    	/**
    	 * Foreign key pointing to right table primary key
    	 * Expected to be joined with {@link #manySideKey}
    	 */
    	private final Key<SELF, RIGHTID> manySideForeignKey;

    	/**
    	 * Primary key of collection entities table
    	 * Expected to be joined with {@link #manySideForeignKey}
    	 */
    	private final PrimaryKey<RIGHTTABLE, RIGHTID> manySideKey;

    	private final Map<Column<LEFTTABLE, ?>, Column<SELF, ?>> leftIdentifierColumnMapping = new HashMap<>();

    	private final Map<Column<RIGHTTABLE, ?>, Column<SELF, ?>> rightIdentifierColumnMapping = new HashMap<>();

    	/**
    	 * @param schema the database schema
    	 * @param name the table name
    	 * @param oneSideKey primary key of the "one" side table
    	 * @param manySideKey primary key of the "many" side table
    	 * @param columnNames column names of the association table
    	 * @param foreignKeyNamingStrategy strategy for naming foreign keys
    	 * @param createOneSideForeignKey whether to create a foreign key for the one side
    	 * @param createManySideForeignKey whether to create a foreign key for the many side
    	 */
    	public AssociationTable(Schema schema,
    							String name,
    							PrimaryKey<LEFTTABLE, LEFTID> oneSideKey,
    							PrimaryKey<RIGHTTABLE, RIGHTID> manySideKey,
    							ReferencedColumnNames<LEFTTABLE, RIGHTTABLE> columnNames,
    							ForeignKeyNamingStrategy foreignKeyNamingStrategy,
    							boolean createOneSideForeignKey,
    							boolean createManySideForeignKey) {
    		super(schema, name);
    		this.oneSideKey = oneSideKey;
    		this.manySideKey = manySideKey;
    		KeyBuilder<SELF, LEFTID> leftForeignKeyBuilder = Key.from((SELF) this);
    		oneSideKey.getColumns().forEach(oneSideKeyColumn -> {
    			Column<SELF, ?> column = addColumn(columnNames.getLeftColumnName(oneSideKeyColumn), oneSideKeyColumn.getJavaType(), oneSideKeyColumn.getSize(), false);
    			column.primaryKey();
    			leftForeignKeyBuilder.addColumn(column);
    			leftIdentifierColumnMapping.put(oneSideKeyColumn, column);
    		});
    		Key<SELF, LEFTID> leftForeignKey = leftForeignKeyBuilder.build();
    		if (createOneSideForeignKey) {
    			this.oneSideForeignKey = addForeignKey(foreignKeyNamingStrategy::giveName, leftForeignKey, oneSideKey);
    		} else {
    			this.oneSideForeignKey = leftForeignKey;
    		}

    		// building many side key (eventually foreign key)
    		KeyBuilder<SELF, RIGHTID> rightForeignKeyBuilder = Key.from((SELF) this);
    		manySideKey.getColumns().forEach(manySideKeyColumn -> {
    			Column<SELF, ?> column = addColumn(columnNames.getRightColumnName(manySideKeyColumn), manySideKeyColumn.getJavaType(), manySideKeyColumn.getSize(), false);
    			column.primaryKey();
    			rightForeignKeyBuilder.addColumn(column);
    			rightIdentifierColumnMapping.put(manySideKeyColumn, column);
    		});
    		Key<SELF, RIGHTID> rightForeignKey = rightForeignKeyBuilder.build();
    		if (createManySideForeignKey) {
    			this.manySideForeignKey = addForeignKey(foreignKeyNamingStrategy::giveName, rightForeignKey, manySideKey);
    		} else {
    			this.manySideForeignKey = rightForeignKey;
    		}
    	}

    	/**
    	 * Gives the foreign key pointing to left table primary key. Expected to be joined with {@link #getOneSideKey()}
    	 * @return the foreign key pointing to left table primary key
    	 */
    	public Key<SELF, LEFTID> getOneSideForeignKey() {
    		return oneSideForeignKey;
    	}

    	/**
    	 * Gives the primary key of source entities table. The one expected to be joined with {@link #getOneSideForeignKey()}
    	 * @return the primary key of source entities table
    	 */
    	public Key<LEFTTABLE, LEFTID> getOneSideKey() {
    		return oneSideKey;
    	}

    	/**
    	 * Gives the foreign key pointing to right table primary key. Expected to be joined with {@link #getManySideKey()}
    	 * @return the foreign key pointing to right table primary key
    	 */
    	public Key<SELF, RIGHTID> getManySideForeignKey() {
    		return manySideForeignKey;
    	}

    	/**
    	 * Gives the primary key of collection entities table. The one expected to be joined with {@link #getManySideForeignKey()}
    	 * @return the primary key of collection entities table
    	 */
    	public Key<RIGHTTABLE, RIGHTID> getManySideKey() {
    		return manySideKey;
    	}

    	public Map<Column<LEFTTABLE, ?>, Column<SELF, ?>> getLeftIdentifierColumnMapping() {
    		return leftIdentifierColumnMapping;
    	}

    	public Map<Column<RIGHTTABLE, ?>, Column<SELF, ?>> getRightIdentifierColumnMapping() {
    		return rightIdentifierColumnMapping;
    	}
    }